package database.sortedset;

import database.inter.IDB;
import database.singleDB.SingleDB;
import datastruct.sortedset.Element;
import datastruct.sortedset.ISortedSetConsumer;
import datastruct.sortedset.JoSortedSet;
import jodis.conn.impl.*;
import jodis.conn.inter.IReply;
import rdb.RDBConstant;
import tcp.Server;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;

import static database.utils.JUtils.judgeExistOption;
import static database.utils.JUtils.makeExpireCmd;

public class JSortedSet {
    public static final int UpsertPolicy = 0;
    public static final int InsertPolicy = 1;
    public static final int UpdatePolicy = 2;

    public static final int SORTED_SET_UPDATE_ALL = 0;
    public static final int SORTED_SET_UPDATE_GREATER = 1;
    public static final int SORTED_SET_UPDATE_LESS_THAN = 2;

    public static final int AGGREGATE_SUM = 0;
    public static final int AGGREGATE_MAX = 1;
    public static final int AGGREGATE_MIN = 2;


    public static JoSortedSet getAsSortedSet(IDB idb, String key) {
        if (idb instanceof SingleDB) {
            SingleDB db = (SingleDB) idb;
            Object obj = db.getEntity(key);
            return (JoSortedSet) obj;
        }
        return null;
    }

    public static JoSortedSet getOrInitSortedSet(IDB idb, String key) {
        JoSortedSet retSortedSet = getAsSortedSet(idb, key);
        if (retSortedSet == null) {
            retSortedSet = JoSortedSet.makeSortedSet();
            SingleDB db = (SingleDB) idb;
            db.putEntity(key, retSortedSet);
            db.putType(key, RDBConstant.TYPE_ZSET);
        }
        return retSortedSet;
    }

    /*
	ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member...]
	 */
    public static IReply execZAdd(IDB idb, String[] cmdLine) {
        boolean isNX, isXX, isGT, isLT, isCH, isINCR;
        isNX = false;
        isXX = false;
        isGT = false;
        isLT = false;
        isCH = false;
        isINCR = false;
        int scoreIndex = -1;
        for (int i=2;i<cmdLine.length;i++) {
            if (!isNX) {
                isNX = cmdLine[i].equalsIgnoreCase("isnx");
            }

            if (!isXX) {
                isXX = cmdLine[i].equalsIgnoreCase("isxx");
            }

            if (!isGT) {
                isGT = cmdLine[i].equalsIgnoreCase("isgt");
            }

            if (!isLT) {
                isLT = cmdLine[i].equalsIgnoreCase("islt");
            }

            if (!isCH) {
                isCH = cmdLine[i].equalsIgnoreCase("isch");
            }

            if (!isINCR) {
                isINCR = cmdLine[i].equalsIgnoreCase("isincr");
            }

            try{
                if (scoreIndex == -1) {
                    Double.parseDouble(cmdLine[i]);
                    scoreIndex = i;

                }
            } catch (NumberFormatException ignore) {}
        }

        // NX GT LT 三个是互斥的
        if (!judgeOptions(isNX, isGT, isLT)) {
            return StandardErrReply.makeErrReply("The GT, LT and NX options are mutually exclusive.");
        }

        // NX XX 是互斥的
        if (!judgeExistOption(isNX, isXX)) {
            return StandardErrReply.makeErrReply("XX and NX options at the same time are not compatible");
        }

        int policy = UpsertPolicy;
        if (isNX) {
            policy = InsertPolicy;
        } else if (isXX) {
            policy = UpdatePolicy;
        }

        int scoreUpdatePolicy = SORTED_SET_UPDATE_ALL;
        if (isGT) {
            scoreUpdatePolicy = SORTED_SET_UPDATE_GREATER;
        } else if (isLT) {
            scoreUpdatePolicy = SORTED_SET_UPDATE_LESS_THAN;
        }

        if (scoreIndex == -1) {
            return StandardErrReply.makeErrReply("syntax error");
        }
//        zadd key nx gt 1 one 2 two
//        8
        int elementSize = (cmdLine.length - scoreIndex)/2;
        if (isINCR && elementSize != 1) {
            return StandardErrReply.makeErrReply("INCR option supports a single increment-element pair");
        }

        int ret = 0;
        String key = cmdLine[1];
        JoSortedSet retSortedSet = getOrInitSortedSet(idb, key);
        int j = 0;
        for (int i=0;i<elementSize;i++,j+=2) {
            double score = Double.parseDouble(cmdLine[scoreIndex+j]);
            String member = cmdLine[scoreIndex+j+1];
            if (retSortedSet.add(score, member, policy, scoreUpdatePolicy, isCH, isINCR)) {
                ret++;
            }
        }
        SingleDB db = (SingleDB) idb;

        db.putType(key, RDBConstant.TYPE_ZSET);

        if (Server.getServerConfig().appendOnly) {
            db.iAddAof.addAof(db.index, cmdLine);
        }
        return IntReply.makeIntReply(ret);
    }

    public static String[][] undoZAdd(IDB idb, String[] cmdLine) {
        String key = cmdLine[1];
        JoSortedSet retSortedSet = getAsSortedSet(idb, key);

        /*
        这是回滚操作，这是在执行 ZAdd 前生成回滚命令
        在 ZAdd 执行前，就不存在对应的 sorted set，所以回滚操作就是删除 key 对应的 sorted set
         */
        if (retSortedSet == null) {
            String[][] ret = new String[1][2];
            ret[0][0] = "Del";
            ret[0][1] = key;
            return ret;
        }
        int scoreIndex = -1;
        for (int i=0;i<cmdLine.length;i++) {
            try {
                int score = Integer.parseInt(cmdLine[i]);
                scoreIndex = i;
                break;
            } catch (NumberFormatException ignore) {}
        }
        if (scoreIndex == -1) {
            return null;
        }
        int elementSize = (cmdLine.length-scoreIndex)/2;
        String[][] undoCmdLines = new String[elementSize][];

        for (int i=0;i<elementSize;i++) {
            String member = cmdLine[scoreIndex+2*i+1];
            Element oldElement = retSortedSet.get(member);
            // 在 zadd 执行前，这个 member 就存在，所以回滚操作就是 添加这个 member 和对应的 score
            if (retSortedSet.contains(member)) {
                undoCmdLines[i] = new String[]{"zadd", key, Double.toString(oldElement.score), member};
            } else {
//                在 zadd 执行前，这个 member 就不存在，所以回滚操作就是 删除这个 member 和对应的 score
                undoCmdLines[i] = new String[]{"zrem", key, member};
            }
        }
        return undoCmdLines;
    }


    public static boolean judgeOptions(boolean isNX, boolean isGT, boolean isLT) {
        return (!isNX && !isGT && !isLT) ||
                (isGT && !isLT && !isNX) ||
                (isLT && !isGT && !isNX) ||
                (isNX && !isGT && !isLT);
    }

    // ZRANGE key start stop [withscores]
    public static IReply execZRange(IDB idb, String[] cmdLines) {
        boolean withScore = cmdLines.length == 5;
        if (cmdLines.length == 5) {
            if (!cmdLines[4].equalsIgnoreCase("withScores")) {
                return StandardErrReply.makeErrReply("syntax error");
            }
        }
        String key = cmdLines[1];
        int start = 0, stop = 0;
        try {
            start = Integer.parseInt(cmdLines[2]);
            stop = Integer.parseInt(cmdLines[3]);
        } catch (NumberFormatException e) {
            return StandardErrReply.makeErrReply("illegal start or stop");
        }

        return range0(idb, key, start, stop, withScore, false);
    }

    private static IReply range0(IDB idb, String key, int start, int stop, boolean withScores, boolean desc) {
        JoSortedSet retSortedSet = getAsSortedSet(idb, key);
        if (retSortedSet == null) {
            return EmptyMultiBulkReply.makeEmptyMultiBulkReply();
        }

        int size = retSortedSet.Len();

        if (start < -1 * size) {
            start = 0;
        } else if (start < 0) {
            start = size + start;
        } else if (start >= size) {
            return EmptyMultiBulkReply.makeEmptyMultiBulkReply();
        }

        if (stop < -1 * size) {
            stop = 0;
        } else if (stop < 0) {
            stop = size + stop + 1;
        } else if (stop < size) {
            stop++;
        } else {
            stop = size;
        }

        if (stop < start) {
            stop = start;
        }

        Element[] retElments = retSortedSet.range(start, stop, desc);

        String[] res = withScores ? new String[retElments.length*2] : new String[retElments.length];

        int i = 0;
        if (withScores) {
            for (Element element : retElments) {
                res[2*i] = element.member;
                res[2*i+1] = Double.toString(element.score);
                i++;
            }
        } else {
            for (Element element : retElments) {
                res[i] = element.member;
                i++;
            }
        }

        return MultiBulkReply.makeMultiBulkReply(res);
    }

    // ZRem key member [member]
    public static IReply execZRem(IDB idb, String[] cmdLines) {
        String key = cmdLines[1];
        JoSortedSet retSortedSet = getAsSortedSet(idb, key);
        if (retSortedSet == null) {
            return EmptyMultiBulkReply.makeEmptyMultiBulkReply();
        }
        int ret = 0;
        for (int i=2;i<cmdLines.length;i++) {
            if (retSortedSet.remove(cmdLines[i])) {
                ret++;
            }
        }
        SingleDB db = (SingleDB) idb;

        if (retSortedSet.Len() == 0) {
            db.removeEntity(key);
        }

        if (Server.getServerConfig().appendOnly) {
            db.iAddAof.addAof(db.index, cmdLines);
        }
        return IntReply.makeIntReply(ret);
    }

    public static String[][] undoZRem(IDB idb, String[] cmdLine) {
        String key = cmdLine[1];
        JoSortedSet retSortedSet = getAsSortedSet(idb, key);
        if (retSortedSet == null) {
            return null;
        }
        int j = 0;
        String[][] undoCmdLines = new String[retSortedSet.Len()][3];
        for (int i=2;i<cmdLine.length;i++) {
            // 在 zrem 命令执行之前，这个 member 就存在，那么回滚时，应该把这个 member 和 对应的 score 再加上
            if (retSortedSet.contains(cmdLine[i])) {
                Element oldElement = retSortedSet.get(cmdLine[i]);
                undoCmdLines[j] = new String[]{"ZAdd", key, Double.toString(oldElement.score), oldElement.member};
            }
        }

        return undoCmdLines;
    }

//    ZCARD key
    public static IReply execZCard(IDB idb, String[] cmdLines) {
        JoSortedSet retSortedSet = getAsSortedSet(idb, cmdLines[1]);
        return retSortedSet == null ? IntReply.makeIntReply(0) : IntReply.makeIntReply(retSortedSet.Len());
    }

//    ZCOUNT key min max
    public static IReply execZCount(IDB idb, String[] cmdLines) {
        if (cmdLines.length != 4) {
            return StandardErrReply.makeErrReply("illegal arguments");
        }

        JoSortedSet retSortedSet = getAsSortedSet(idb, cmdLines[1]);
        if (retSortedSet == null || retSortedSet.Len() == 0) {
            return IntReply.makeIntReply(0);
        }

        boolean isExclusiveMin = cmdLines[2].charAt(0) == '(', isExclusiveMax = cmdLines[3].charAt(0) == '(';

        try {
            double min = new BigDecimal(isExclusiveMin ? cmdLines[2].substring(1) : cmdLines[2]).doubleValue();
            double max = new BigDecimal(isExclusiveMax ? cmdLines[3].substring(1) : cmdLines[3]).doubleValue();

            System.out.printf("min: %f\n", min);
            System.out.printf("max: %f\n", max);

            final int[] ret = {0};
            retSortedSet.forEach(false, new ISortedSetConsumer() {
                @Override
                public boolean consumer(String key, Element val) {
                    BigDecimal sc = BigDecimal.valueOf(val.score);
                    if (isExclusiveMin && val.score > min && val.score <= max && !isExclusiveMax) {
                        System.out.println("左开右闭");
                        ret[0]++;
                    } else if (!isExclusiveMin && val.score >= min && val.score < max && isExclusiveMax) {
                        System.out.println("左闭右开");
                        // 左闭右开
                        ret[0]++;
                    } else if (!isExclusiveMin && val.score >= min && val.score <= max && !isExclusiveMax) {
                        System.out.println("左闭右闭");
                        // 左闭右闭
                        ret[0]++;
                    } else if (isExclusiveMin && val.score > min && val.score < max && isExclusiveMax){
                        System.out.println("左开右开");
                        // 左开右开
                        ret[0]++;
                    }
                    return true;
                }
            });
            return IntReply.makeIntReply(ret[0]);
        } catch (NumberFormatException e) {
            return StandardErrReply.makeErrReply(e.getMessage());
        }
    }

//    ZREVRANGE key start stop [WITHSCORES]
    public static IReply execZRevRange(IDB idb, String[] cmdLines) {
        boolean withScore = cmdLines.length == 5;
        if (cmdLines.length == 5) {
            if (!cmdLines[4].equalsIgnoreCase("withScores")) {
                return StandardErrReply.makeErrReply("syntax error");
            }
        }
        String key = cmdLines[1];
        int start = 0, stop = 0;
        try {
            start = Integer.parseInt(cmdLines[2]);
            stop = Integer.parseInt(cmdLines[3]);
        } catch (NumberFormatException e) {
            return StandardErrReply.makeErrReply("illegal start or stop");
        }

        return range0(idb, key, start, stop, withScore, true);
    }


//  从 7.2.0 开始，才支持 withscores 选项
//  ZRANK key member [WITHSCORE]
    public static IReply execZRank(IDB idb, String[] cmdLines) {
        return rank0(idb, cmdLines, false);
    }

    public static IReply execZRevRank(IDB idb, String[] cmdLines) {
        return rank0(idb, cmdLines, true);
    }

//    ZRANK key member [WITHSCORE]
    private static IReply rank0(IDB idb, String[] cmdLines, boolean desc) {
        if (cmdLines.length < 3) {
            return StandardErrReply.makeErrReply("illegal arguments");
        }

        JoSortedSet retSortedSet = getAsSortedSet(idb, cmdLines[1]);
        if (retSortedSet == null || retSortedSet.Len() == 0) {
            return NullReply.makeNullReply();
        }

        boolean withScore = cmdLines.length == 4 && cmdLines[3].equalsIgnoreCase("withscore");

        final int[] rank = {0};
        final double[] sc = {0};
        if (!retSortedSet.contains(cmdLines[2])) {
            return NullReply.makeNullReply();
        }
        retSortedSet.forEach(desc, new ISortedSetConsumer() {
            @Override
            public boolean consumer(String key, Element val) {
                if (key.equals(cmdLines[2])) {
                    sc[0] = val.score;
                    return false;
                }
                rank[0]++;
                return true;
            }
        });

        return withScore ? MultiBulkReply.makeMultiBulkReply(new String[]{Integer.toString(rank[0]), Double.toString(sc[0])}) : IntReply.makeIntReply(rank[0]);
    }

    //  ZSCORE key member
    public static IReply execZScore(IDB idb, String[] cmdLines) {
        JoSortedSet retSortedSet = getAsSortedSet(idb, cmdLines[1]);
        if (retSortedSet == null || !retSortedSet.contains(cmdLines[2])) {
            return NullReply.makeNullReply();
        }

        return MultiBulkReply.makeMultiBulkReply(new String[]{Double.toString(retSortedSet.get(cmdLines[2]).score)});
    }

    // ZDIFFSTORE destination numkeys key [key ...]
    public static IReply execZDiffStore(IDB idb, String[] cmdLines) {
        if (cmdLines.length < 4) {
            return StandardErrReply.makeErrReply("illegal number of arguments");
        }

        JoSortedSet retSortedSet = getOrInitSortedSet(idb, cmdLines[1]);
        retSortedSet.removeAll();

        try {
            int numKeys = Integer.parseInt(cmdLines[2]);
            JoSortedSet firstSet = getAsSortedSet(idb, cmdLines[3]);
            JoSortedSet[] restSets = new JoSortedSet[numKeys-1];
            for (int i=4, j=0;i<cmdLines.length;i++, j++) {
                restSets[j] = getAsSortedSet(idb, cmdLines[i]);
            }

            String[] content = firstSet.diff(restSets);
            for (int i=0;i<content.length-1;i+=2) {
                retSortedSet.add(Double.parseDouble(content[i+1]), content[i]);
            }
            return IntReply.makeIntReply(content.length/2);
        } catch (NumberFormatException e) {
            return StandardErrReply.makeErrReply(e.getMessage());
        }
    }

    // ZDIFFSTORE destination numkeys key [key ...]
    // 这里只对 numkeys 个 key 加写锁，destination 通过 Command::getFirstKey 加读锁
    public static String[] prepareZDiffStore(String[] cmdLines) {
        int numKeys = Integer.parseInt(cmdLines[2]);
        String[] ret = new String[numKeys];
        int j = 0, i = 3;
        while (j < numKeys) {
            ret[j++] = cmdLines[i++];
        }
        return ret;
    }

//     ZDIFFSTORE destination numkeys key [key ...]
    public static String[][] undoZDiffStore(IDB idb, String[] cmdLines) {
        JoSortedSet retSortedSet = getAsSortedSet(idb, cmdLines[1]);
        if (retSortedSet == null) {
            return new String[][]{
                    {"Del", cmdLines[1]}
            };
        }

        String[] res = new String[2+retSortedSet.Len()*2];
        res[0] = "ZAdd";
        res[1] = cmdLines[1];
        final int[] i = {2};
        retSortedSet.forEach(false, new ISortedSetConsumer() {
            @Override
            public boolean consumer(String key, Element val) {
                res[i[0]] = key;
                res[i[0]+1] = Double.toString(val.score);
                i[0] += 2;
                return true;
            }
        });

        return new String[][]{
                res
        };
    }


    // ZDIFF numkeys key [key ...] [withScores]
//    ZDIFF 2 zset1 zset2 WITHSCORES
    public static IReply execZDiff(IDB idb, String[] cmdLines) {
        if (cmdLines.length < 3) {
            return StandardErrReply.makeErrReply("illegal number of arguments");
        }
        boolean withScores = cmdLines[cmdLines.length-1].equalsIgnoreCase("withscores");
        try {
            int numKeys = Integer.parseInt(cmdLines[1]);
            JoSortedSet firstSet = getAsSortedSet(idb, cmdLines[2]);
            JoSortedSet[] restSets = new JoSortedSet[numKeys-1];
            int i = 3, j = 0;
            for (j=0;j<restSets.length;i++, j++) {
                restSets[j] = getAsSortedSet(idb, cmdLines[i]);
            }
            String[] content = firstSet.diff(restSets);
            if (!withScores) {
                String[] withoutScores = new String[content.length/2];
                for (i=0,j=0;i<content.length;i+=2,j++) {
                    withoutScores[j] = content[i];
                }
                return MultiBulkReply.makeMultiBulkReply(withoutScores);
            }
            return MultiBulkReply.makeMultiBulkReply(content);
        } catch (NumberFormatException e) {
            return StandardErrReply.makeErrReply(e.getMessage());
        }
    }

    // ZDIFF numkeys key [key ...] [withScores]
    public static String[] prepareZDiff(String[] cmdLines) {
        int numKeys = Integer.parseInt(cmdLines[1]);
        return Arrays.copyOfRange(cmdLines, 2, 2+numKeys);
    }

//    ZINCRBY key increment member
    public static IReply execZIncrBy(IDB idb, String[] cmdLines) {
        if (cmdLines.length != 4) {
            return StandardErrReply.makeErrReply("illegal arguments");
        }

        JoSortedSet sortedSet = getOrInitSortedSet(idb, cmdLines[1]);

        try {
            double score = Double.parseDouble(cmdLines[2]);
            if (!sortedSet.contains(cmdLines[3])) {
                sortedSet.add(score, cmdLines[3]);
                return MultiBulkReply.makeMultiBulkReply(new String[]{cmdLines[2]});
            }

            double oldScore = sortedSet.get(cmdLines[3]).score;
            sortedSet.add(oldScore+score, cmdLines[3]);
            return MultiBulkReply.makeMultiBulkReply(new String[]{Double.toString(oldScore+score)});
        } catch (NumberFormatException e) {
            return StandardErrReply.makeErrReply(e.getMessage());
        }
    }

    //    ZINCRBY key increment member
    public static String[][] undoZIncrBy(IDB idb, String[] cmdLines) {
        JoSortedSet retSortedSet = getAsSortedSet(idb, cmdLines[1]);
        if (retSortedSet == null) {
            return new String[][]{
                    {"Del", cmdLines[1]}
            };
        }

        if (!retSortedSet.contains(cmdLines[3])) {
            return new String[][] {
                    {"ZRem", cmdLines[1], cmdLines[3]}
            };
        }

        double oldScore = retSortedSet.get(cmdLines[3]).score;

        return new String[][] {
                {"ZAdd", cmdLines[1], Double.toString(oldScore), cmdLines[3]}
        };
    }

//    ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight
//  [weight ...]] [AGGREGATE <SUM | MIN | MAX>]
    public static IReply execZUnionStore(IDB idb, String[] cmdLines) {
        if (cmdLines.length < 4) {
            return StandardErrReply.makeErrReply("illegal arguments");
        }

        int weightsIndex = -1, aggregateIndex = -1;
        for (int i=0;i<cmdLines.length;i++) {
            if (cmdLines[i].equalsIgnoreCase("weights")) {
                weightsIndex = i;
            } else if (cmdLines[i].equalsIgnoreCase("aggregate")) {
                aggregateIndex = i;
            }
        }

        try {
            int numKeys = Integer.parseInt(cmdLines[2]);
            double[] weights = new double[numKeys];
            int i = weightsIndex + 1;
            int j = 0;
            while (j<numKeys) {
                weights[j++] = weightsIndex == -1 ? 1.0 : Double.parseDouble(cmdLines[i++]);
            }

            int aggregate = JSortedSet.AGGREGATE_SUM;
            if (aggregateIndex != -1) {
                if (cmdLines[aggregateIndex+1].equalsIgnoreCase("max")) {
                    aggregate = JSortedSet.AGGREGATE_MAX;
                } else if (cmdLines[aggregateIndex+1].equalsIgnoreCase("min")) {
                    aggregate = JSortedSet.AGGREGATE_MIN;
                }
            }

            JoSortedSet[] sets = new JoSortedSet[numKeys];
            for (j=0,i=3;j<numKeys;j++,i++) {
                sets[j] = getAsSortedSet(idb, cmdLines[i]);
            }

            JoSortedSet ret = JoSortedSet.union(sets, weights, aggregate);
            SingleDB db = (SingleDB) idb;
            db.putEntity(cmdLines[1], ret);
            db.putType(cmdLines[1], RDBConstant.TYPE_ZSET);
            return IntReply.makeIntReply(ret.Len());
        } catch (NumberFormatException e) {
            return StandardErrReply.makeErrReply(e.getMessage());
        }

    }

    //    ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight
//  [weight ...]] [AGGREGATE <SUM | MIN | MAX>]
    // 这里只对 numkeys 个 key 加写锁，destination 加读锁（读锁通过 Command::getFirstKey 加，这里不加）
    public static String[] prepareZUnionStore(String[] cmdLines) {
        int numKeys = Integer.parseInt(cmdLines[2]);
        String[] ret = new String[numKeys];
        int i = 0, j = 3;
        while (i < numKeys) {
            ret[i++] = cmdLines[j++];
        }
        return ret;
    }

    public static String[][] undoZUnionStore(IDB idb, String[] cmdLines) {
        return undoZDiffStore(idb, cmdLines);
    }

    //    ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight
//  [weight ...]] [AGGREGATE <SUM | MIN | MAX>]
    public static IReply execZInterStore(IDB idb, String[] cmdLines) {
        if (cmdLines.length < 4) {
            return StandardErrReply.makeErrReply("illegal arguments");
        }

        int weightsIndex = -1, aggregateIndex = -1;
        for (int i=0;i<cmdLines.length;i++) {
            if (cmdLines[i].equalsIgnoreCase("weights")) {
                weightsIndex = i;
            } else if (cmdLines[i].equalsIgnoreCase("aggregate")) {
                aggregateIndex = i;
            }
        }

        try {
            int numKeys = Integer.parseInt(cmdLines[2]);
            double[] weights = new double[numKeys];
            int i = weightsIndex + 1;
            int j = 0;
            while (j<numKeys) {
                weights[j++] = weightsIndex == -1 ? 1.0 : Double.parseDouble(cmdLines[i++]);
            }

            int aggregate = JSortedSet.AGGREGATE_SUM;
            if (aggregateIndex != -1) {
                if (cmdLines[aggregateIndex+1].equalsIgnoreCase("sum")) {
                    aggregate = JSortedSet.AGGREGATE_SUM;
                } else if (cmdLines[aggregateIndex+1].equalsIgnoreCase("max")) {
                    aggregate = JSortedSet.AGGREGATE_MAX;
                } else if (cmdLines[aggregateIndex+1].equalsIgnoreCase("min")) {
                    aggregate = JSortedSet.AGGREGATE_MIN;
                }
            }

            JoSortedSet[] sets = new JoSortedSet[numKeys];
            for (j=0,i=3;j<numKeys;j++,i++) {
                sets[j] = getAsSortedSet(idb, cmdLines[i]);
            }

            JoSortedSet ret = JoSortedSet.intersection(sets, weights, aggregate);
            SingleDB db = (SingleDB)idb;
            db.putEntity(cmdLines[1], ret);
            db.putType(cmdLines[1], RDBConstant.TYPE_ZSET);

            return ret == null ? NullReply.makeNullReply() : IntReply.makeIntReply(ret.Len());
        } catch (NumberFormatException e) {
            return StandardErrReply.makeErrReply(e.getMessage());
        }
    }

    //    ZInterSTORE destination numkeys key [key ...] [WEIGHTS weight
//  [weight ...]] [AGGREGATE <SUM | MIN | MAX>]
    public static String[] prepareZInterStore(String[] cmdLines) {
        return prepareZUnionStore(cmdLines);
    }

    public static String[][] undoZInterStore(IDB idb, String[] cmdLines) {
        return undoZDiffStore(idb, cmdLines);
    }

//    ZPOPMIN key [count]
    public static IReply execZPopMin(IDB idb, String[] cmdLines) {
        int count = 1;
        if (cmdLines.length == 3) {
            try {
                count = Integer.parseInt(cmdLines[2]);
            } catch (NumberFormatException e) {
                return StandardErrReply.makeErrReply(e.getMessage());
            }
        }

        JoSortedSet sortedSet = getAsSortedSet(idb, cmdLines[1]);
        if (sortedSet == null || sortedSet.Len() == 0) {
            return EmptyMultiBulkReply.makeEmptyMultiBulkReply();
        }

        count = Math.min(count, sortedSet.Len());
        final int[] i = {0};
        String[] res = new String[count*2];
        int finalCount = count;
        sortedSet.forEach(false, new ISortedSetConsumer() {
            @Override
            public boolean consumer(String key, Element val) {
                if (i[0] == finalCount*2) {
                    return false;
                }
                res[i[0]] = key;
                res[i[0] +1] = Double.toString(val.score);
                i[0] += 2;
                return true;
            }
        });

        for(int j=0;j<res.length-1;j+=2) {
            sortedSet.remove(res[j]);
        }

        return MultiBulkReply.makeMultiBulkReply(res);
    }

    public static String[][] undoPopMin(IDB idb, String[] cmdLines) {
        JoSortedSet retSortedSet = getAsSortedSet(idb, cmdLines[1]);
        if (retSortedSet == null) {
            return new String[][]{
                    {"Del", cmdLines[1]}
            };
        }

        SingleDB db = (SingleDB) idb;
        Long ttl = db.getTTL(cmdLines[1]);
        int size = retSortedSet.Len();
        if (ttl > System.currentTimeMillis()) {
            String[][] ret = new String[2][];
            ret[0] = new String[]{"PExpireAt", cmdLines[1], Long.toString(ttl)};
            ret[1] = new String[2+size*2];
            ret[1][0] = "ZAdd";
            ret[1][1] = cmdLines[1];

            final int[] i = {2};
            retSortedSet.forEach(false, new ISortedSetConsumer() {
                @Override
                public boolean consumer(String key, Element val) {
                    ret[1][i[0]] = key;
                    ret[1][i[0] +1] = Double.toString(val.score);
                    i[0] += 2;
                    return true;
                }
            });

            return ret;
        }

        String[] ret = new String[2+size*2];
        ret[0] = "ZAdd";
        ret[1] = cmdLines[1];

        final int[] i = {2};
        retSortedSet.forEach(false, new ISortedSetConsumer() {
            @Override
            public boolean consumer(String key, Element val) {
                ret[i[0]] = key;
                ret[i[0] +1] = Double.toString(val.score);
                i[0] += 2;
                return true;
            }
        });

        return new String[][] {
                ret
        };
    }


}


